package org.terramagnet.pdmviewer.parser;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.math.NumberUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

public class Parser {

	public PDM parsePdm(File file) throws FileNotFoundException, DocumentException {
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);
			return parsePdm(fis);
		} finally {
			try {
				if (fis != null) {
					fis.close();
				}
			} catch (IOException ex) {
				//this cannot happen
			}
		}

	}

	public PDM parsePdm(InputStream is) throws DocumentException {
		SAXReader reader = new SAXReader();
		Document doc = reader.read(is);
		Node model = doc.selectSingleNode("//c:Children/o:Model");

		PDM pdm = new PDM();
		pdm.setId(((Element) model).attributeValue("Id"));
		pdm.setName(model.selectSingleNode("a:Name").getText());
		pdm.setCode(model.selectSingleNode("a:Code").getText());

		Node dbms = model.selectSingleNode("//o:Shortcut");
		pdm.setDbmsCode(dbms.selectSingleNode("a:Code").getText());
		pdm.setDbmsName(dbms.selectSingleNode("a:Name").getText());

		for (Object o : model.selectNodes("c:Users/o:User")) {
			Node userNode = (Node) o;
			PDMUser pdmUser = new PDMUser();
			pdmUser.setId(((Element) userNode).attributeValue("Id"));
			pdmUser.setName(userNode.selectSingleNode("a:Name").getText());
			pdmUser.setCode(userNode.selectSingleNode("a:Code").getText());

			pdm.getUsers().put(pdmUser.getId(), pdmUser);
		}

		parseTables(pdm, model);
		parsePhysicalDiagrams(pdm, model);
		parseReferences(pdm, model);

		return pdm;
	}

	private void parseTables(PDM pdm, Node root) {
		for (Object o : root.selectNodes("c:Tables/o:Table")) {
			Node tableNode = (Node) o;
			PDMTable pdmTable = new PDMTable();
			pdmTable.setId(((Element) tableNode).attributeValue("Id"));
			pdmTable.setName(tableNode.selectSingleNode("a:Name").getText());
			pdmTable.setCode(tableNode.selectSingleNode("a:Code").getText());
			// 添加Columns
			parseColumn(pdmTable, tableNode);
			// 添加key
			for (Object key : tableNode.selectNodes("c:Keys/o:Key")) {
				Node keyNode = (Node) key;
				PDMKey pdmKey = new PDMKey();
				pdmKey.setId(((Element) keyNode).attributeValue("Id"));
				pdmKey.setName(keyNode.selectSingleNode("a:Name").getText());
				pdmKey.setCode(keyNode.selectSingleNode("a:Code").getText());
				for (Object column : keyNode.selectNodes("c:Key.Columns/o:Column")) {
					String id = ((Element) column).attributeValue("Ref");
					pdmKey.getColumns().put(id, pdmTable.getColumns().get(id));
				}
				pdmTable.getKeys().put(pdmKey.getId(), pdmKey);
			}
			// 添加PrimaryKey
			Element keyNode = ((Element) tableNode.selectSingleNode("c:PrimaryKey/o:Key"));
			if (keyNode != null) {
				String keyId = keyNode.attributeValue("Ref");
				pdmTable.setPrimaryKey(pdmTable.getKeys().get(keyId));
				//为 primaryKey 的字段设置 primary 标识
				Iterator<PDMColumn> iterator = pdmTable.getPrimaryKey().getColumns().values().iterator();
				while(iterator.hasNext()){
					iterator.next().setPrimary(true);
				}
			}
			// 添加Indexes
			for (Object index : tableNode.selectNodes("c:Indexes/o:Index")) {
				Node indexNode = (Node) index;
				PDMIndex pdmIndex = new PDMIndex();
				pdmIndex.setId(((Element) indexNode).attributeValue("Id"));
				pdmIndex.setName(indexNode.selectSingleNode("a:Name").getText());
				pdmIndex.setCode(indexNode.selectSingleNode("a:Code").getText());
				for (Object column : indexNode.selectNodes("//c:Column/o:Column")) {
					String id = ((Element) column).attributeValue("Ref");
					pdmIndex.getColumns().put(id, pdmTable.getColumns().get(id));
				}
				pdmTable.getIndexs().put(pdmIndex.getId(), pdmIndex);
			}
			// 添加User
			Element userElement = (Element) tableNode.selectSingleNode("c:Owner/o:User");
			if (userElement != null) {
				String userId = userElement.attributeValue("Ref");
				pdmTable.setUser(userId);
			}

			pdm.getTables().put(pdmTable.getId(), pdmTable);
		}
	}

	private void parseColumn(PDMTable pdmTable, Node tableNode) {
		for (Object o : tableNode.selectNodes("c:Columns/o:Column")) {
			Node columnNode = (Node) o;
			PDMColumn pdmColumn = new PDMColumn();
			pdmColumn.setId(((Element) columnNode).attributeValue("Id"));
			pdmColumn.setName(columnNode.selectSingleNode("a:Name").getText());
			pdmColumn.setCode(columnNode.selectSingleNode("a:Code").getText());
			pdmColumn.setDataType(columnNode.selectSingleNode("a:DataType").getText());
			pdmColumn.setLength(selectSingleNodeIntText(columnNode, "a:Length"));
			pdmColumn.setPrecision(selectSingleNodeIntText(columnNode, "a:Precision"));
			pdmColumn.setMandatory(selectSingleNodeIntText(columnNode, "a:Mandatory"));
			pdmColumn.setDefaultValue(selectSingleNodeStringText(columnNode, "a:DefaultValue"));
			pdmColumn.setLowValue(selectSingleNodeStringText(columnNode, "a:LowValue"));
			pdmColumn.setHighValue(selectSingleNodeStringText(columnNode, "a:HighValue"));
			pdmColumn.setComment(selectSingleNodeStringText(columnNode, "a:Comment"));
			pdmTable.getColumns().put(pdmColumn.getId(), pdmColumn);
		}
	}

	private void parsePhysicalDiagrams(PDM pdm, Node root) {
		for (Object o : root.selectNodes("c:PhysicalDiagrams/o:PhysicalDiagram")) {
			Node physicalNode = (Node) o;
			PDMPhysicalDiagram pdmPhysical = new PDMPhysicalDiagram();
			pdmPhysical.setId(((Element) physicalNode).attributeValue("Id"));
			pdmPhysical.setName(physicalNode.selectSingleNode("a:Name").getText());
			pdmPhysical.setCode(physicalNode.selectSingleNode("a:Code").getText());

			for (Object obj : physicalNode.selectNodes("c:Symbols/o:RectangleSymbol")) {
				Element rectangle = ((Element) obj);
				PDMRectangleShape shape = new PDMRectangleShape();
				shape.setId(rectangle.attributeValue("Id"));
				shape.setBackgroundColor(selectSingleNodeColorValue(rectangle, "a:FillColor"));
				shape.setBorderColor(selectSingleNodeColorValue(rectangle, "a:LineColor"));
				shape.setShadowColor(selectSingleNodeColorValue(rectangle, "a:ShadowColor"));
				parseRect(shape, rectangle, "a:Rect");
				pdmPhysical.getShapes().add(shape);
			}

			for (Object obj : physicalNode.selectNodes("c:Symbols/o:RoundRectangleSymbol")) {
				Element rectangle = ((Element) obj);
				PDMRoundRectangleShape shape = new PDMRoundRectangleShape();
				shape.setId(rectangle.attributeValue("Id"));
				shape.setBackgroundColor(selectSingleNodeColorValue(rectangle, "a:FillColor"));
				shape.setBorderColor(selectSingleNodeColorValue(rectangle, "a:LineColor"));
				shape.setShadowColor(selectSingleNodeColorValue(rectangle, "a:ShadowColor"));
				parseRect(shape, rectangle, "a:Rect");

				pdmPhysical.getShapes().add(shape);
			}

			for (Object obj : physicalNode.selectNodes("c:Symbols/o:TableSymbol")) {
				Element rectangle = ((Element) obj);
				PDMTableShape shape = new PDMTableShape();
				shape.setId(rectangle.attributeValue("Id"));
				shape.setBackgroundColor(selectSingleNodeColorValue(rectangle, "a:FillColor"));
				shape.setBorderColor(selectSingleNodeColorValue(rectangle, "a:LineColor"));
				shape.setShadowColor(selectSingleNodeColorValue(rectangle, "a:ShadowColor"));
				parseRect(shape, rectangle, "a:Rect");

				Element table = (Element) rectangle.selectSingleNode("c:Object/o:Table");
				String id = table.attributeValue("Ref");
				shape.setTable(id);
				pdmPhysical.getShapes().add(shape);
			}

			pdm.getPhysicalDiagrams().put(pdmPhysical.getId(), pdmPhysical);
		}
	}

	private void parseReferences(PDM pdm, Node root) {
		for (Object reference : root.selectNodes("c:References/o:Reference")) {
			Node referenceNode = (Node) reference;
			PDMReference pdmReference = new PDMReference();
			pdmReference.setId(((Element) referenceNode).attributeValue("Id"));
			pdmReference.setName(referenceNode.selectSingleNode("a:Name").getText());
			pdmReference.setCode(referenceNode.selectSingleNode("a:Code").getText());
			pdmReference.setConstraintName(selectSingleNodeStringText(referenceNode, "ForeignKeyConstraintName"));
			pdmReference.setUpdateConstraint(selectSingleNodeIntText(referenceNode, "UpdateConstraint"));
			pdmReference.setDeleteConstraint(selectSingleNodeIntText(referenceNode, "DeleteConstraint"));
			pdmReference.setImplementationType(selectSingleNodeStringText(referenceNode, "ImplementationType"));
			// 添加ParentTable
			String parentTableId = ((Element) referenceNode
				.selectSingleNode("c:ParentTable/o:Table"))
				.attributeValue("Ref");
			pdmReference.setParentTable(parentTableId);
			// 添加ChildTable
			String childTableId = ((Element) referenceNode
				.selectSingleNode("c:ChildTable/o:Table"))
				.attributeValue("Ref");
			pdmReference.setChildTable(childTableId);
			// 添加Joins
			for (Object jion : referenceNode.selectNodes("c:Joins/o:ReferenceJoin")) {
				Node referenceJoinNode = (Node) jion;
				PDMReferenceJoin pdmReferenceJoin = new PDMReferenceJoin();
				pdmReferenceJoin.setId(((Element) referenceJoinNode).attributeValue("Id"));

				String id = ((Element) referenceJoinNode
					.selectSingleNode("c:Object1/o:Column"))
					.attributeValue("Ref");
				pdmReferenceJoin.setParentTable_Col(id);

				id = ((Element) referenceJoinNode
					.selectSingleNode("c:Object2/o:Column"))
					.attributeValue("Ref");
				pdmReferenceJoin.setChildTable_Col(id);
				pdmReference.getJoins().put(pdmReference.getId(), pdmReferenceJoin);
			}

			pdm.getReferences().put(pdmReference.getId(), pdmReference);
		}
	}

	private String selectSingleNodeStringText(Node parentNode, String childNodeName) {
		Node childNode = parentNode.selectSingleNode(childNodeName);
		if (childNode != null) {
			return childNode.getText();
		} else {
			return null;
		}
	}

	private int selectSingleNodeIntText(Node parentNode, String childNodeName) {
		Node childNode = parentNode.selectSingleNode(childNodeName);
		if (childNode != null) {
			try {
				return Integer.parseInt(childNode.getText());
			} catch (NumberFormatException ex) {
				return 0;
			}
		} else {
			return 0;
		}
	}

	private String selectSingleNodeColorValue(Node parentNode, String childNodeName) {
		Node childNode = parentNode.selectSingleNode(childNodeName);
		long l = NumberUtils.toLong(childNode.getText(), 0);
		return Long.toHexString(l);
	}
	private static final Pattern axisRegex = Pattern.compile("\\(\\((\\-?\\d+),(\\-?\\d+)\\),\\s?\\((\\-?\\d+),(\\-?\\d+)\\)\\)", Pattern.CASE_INSENSITIVE);

	private void parseRect(PDMRectangleShape rectangle, Element node, String childNodeName) {
		String s = node.selectSingleNode(childNodeName).getText();
		Matcher matcher = axisRegex.matcher(s);
		if (matcher.find()) {
			rectangle.setX(NumberUtils.toFloat(matcher.group(1), 0) / 74.9f);
			rectangle.setY(NumberUtils.toFloat(matcher.group(2), 0) / 74.9f);
			float x2 = NumberUtils.toFloat(matcher.group(3), 2) / 74.9f;
			float y2 = NumberUtils.toFloat(matcher.group(4), 2) / 74.9f;
			rectangle.setWidth(x2 - rectangle.getX());
			rectangle.setHeight(y2 - rectangle.getY());
		}
	}

}
